package cn.monkey.gateway.stream.auth.rpc;

import cn.monkey.gateway.data.Result;
import cn.monkey.gateway.data.Results;
import cn.monkey.gateway.stream.auth.AbstractAuthClient;
import cn.monkey.gateway.stream.auth.config.AuthConfigProperties;
import cn.monkey.gateway.utils.NetUtils;
import org.springframework.http.*;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.lang.NonNull;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

public class RemoteAuthClient extends AbstractAuthClient {

    private final WebClient.Builder webClientBuilder;

    private final AuthConfigProperties authConfigProperties;

    public RemoteAuthClient(WebClient.Builder webClientBuilder,
                            AuthConfigProperties authConfigProperties) {
        this.webClientBuilder = webClientBuilder;
        this.authConfigProperties = authConfigProperties;
    }

    protected Mono<Result<?>> decode(ClientResponse clientResponse, byte[] body) {
        HttpHeaders httpHeaders = clientResponse.headers().asHttpHeaders();
        MediaType contentType = httpHeaders.getContentType();
        if (isJsonContentType(contentType)) {
            return Mono.just(Results.fromJsonStr(new String(body), Void.class));
        }

        // TODO other media types
        return Mono.just(Results.ok());
    }

    protected Mono<Result<?>> decodeErrorResponseBody(ClientResponse clientResponse) {
        HttpHeaders httpHeaders = clientResponse.headers().asHttpHeaders();
        MediaType contentType = httpHeaders.getContentType();
        HttpStatusCode httpStatusCode = clientResponse.statusCode();
        if (isJsonContentType(contentType)) {
            return clientResponse.bodyToMono(byte[].class)
                    .map(bytes -> Results.fail(httpStatusCode.value(), new String(bytes)));
        }
        // TODO other media types
        return Mono.just(Results.fail(httpStatusCode.value(), ""));
    }

    @Override
    @NonNull
    public Mono<Result<?>> check(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        final WebClient webClient = webClientBuilder.build();
        return webClient.post()
                .uri(this.authConfigProperties.getRequest().getPath())
                .headers(httpHeaders -> httpHeaders.addAll(NetUtils.buildJsonContentHeaders(request)))
                .bodyValue(NetUtils.buildBody(request))
                .exchangeToMono(response -> {
                    HttpStatusCode httpStatusCode = response.statusCode();
                    if (HttpStatus.OK == httpStatusCode) {
                        return response.bodyToMono(byte[].class)
                                .flatMap(bytes -> this.decode(response, bytes));
                    }
                    return this.decodeErrorResponseBody(response);
                })
                .onErrorResume(e -> {
                    if (e instanceof ResponseStatusException rse) {
                        HttpStatusCode statusCode = rse.getStatusCode();
                        return Mono.just(Results.fail(statusCode.value(), rse.getMessage()));
                    }
                    return Mono.just(Results.error(e));
                });
    }
}
